<?php
/*********************************************
 *  CPG Dragonfly™ CMS
 *********************************************
	Copyright © since 2010 by CPG-Nuke Dev Team
	http://dragonflycms.org

	Dragonfly is released under the terms and conditions
	of the GNU GPL version 2 or any later version
*/

# http://www.unc.edu/~rowlett/units/codes/country.htm
# http://www.w3.org/WAI/ER/IG/ert/iso639.htm
# http://www.loc.gov/standards/iso639-2/
# http://www.unicode.org/onlinedat/countries.html
# ftp://ftp.ilog.fr/pub/Users/haible/utf8/ISO_3166
# ftp://ftp.ilog.fr/pub/Users/haible/utf8/Unicode-HOWTO.html

class Dragonfly_L10N implements ArrayAccess
{
	protected
		$id = null,
		$lng = null;

	protected static
		$ua_lng = null,
		$data = array(),
		$all  = null;

	const REGEX = '#^([a-z]{2,3})(-[a-z]{2})?$#D';

	function __construct()
	{
		if (is_null(self::$ua_lng))
		{
			$K = Dragonfly::getKernel();
			self::$ua_lng = false;
			$lngs = array();

			if (isset($_GET['newlang']) && preg_match(self::REGEX, $_GET['newlang']))
			{
				$lngs[8] = $_GET['newlang'];
			}

			# get user session language
			if (!empty($_SESSION['Dragonfly']['lang']) && self::active($_SESSION['Dragonfly']['lang']))
			{
				$lngs[7] = $_SESSION['Dragonfly']['lang'];
			}

			if (!empty($K->IDENTITY) && $K->IDENTITY->isMember())
			{
				$lngs[6] = $K->IDENTITY->language;
			}
/*
			if (isset($_COOKIE['lang']) && preg_match(self::REGEX, $_COOKIE['lang']))
			{
				$lngs[5] = $_COOKIE['lang'];
			}
*/
			# get user agent languages
			if (!empty(Dragonfly::$UA_LANGUAGES))
			{
				# split and sort accepted languages by rank (q)
				if (preg_match_all('#(?:^|,)([a-z\-]+)(?:;\s*q=([0-9.]+))?#', Dragonfly::$UA_LANGUAGES, $accepted_languages, PREG_SET_ORDER))
				{
					foreach ($accepted_languages as $lang) {
						if (!isset($lang[2])) { $lang[2] = 1; }
						if (2 > $lang[2]) {
							$lngs[sprintf('%f%d', $lang[2], rand(0,9999))] = $lang[1];
						}
						else { $lngs[$lang[2]] = $lang[1]; }
					}
				}
			}

			# get default language
			if (is_object($K) && is_object($K->CFG)) {
				$lngs[0] = $K->CFG->global->language;
			}

			# check acceptance
			krsort($lngs);
			foreach ($lngs as $dummy => $lng) {
				if (!self::active($lng) && 3<strlen($lng)) {
					$lng = substr($lng, 0, -3);
				}
				if (self::active($lng)) {
					self::$ua_lng = $lng;
					break;
				}
			}
			if (!empty($lngs[9]) && $lngs[9] !== self::$ua_lng) {
//				URI::redirect(str_replace("/{$lngs[9]}/", '/', $_SERVER['REQUEST_URI']));
			}

			if (!empty($_SESSION))
			{
				$_SESSION['Dragonfly']['lang'] = self::$ua_lng;
			}
//			setcookie('lang', $currentlang, time()+31536000, self::$ua_lng);
		}

		$this->__set('lng', self::$ua_lng ? self::$ua_lng : 'en');
	}

	function __get($key)
	{
		if ('id' === $key) {
			if (!is_int($this->id)) {
				$this->id = 0;
				$SQL = Dragonfly::getKernel()->SQL;
				if ($SQL && isset($SQL->TBL->l10n) && $r = $SQL->uFetchRow("SELECT l10n_id FROM {$SQL->TBL->l10n} WHERE l10n_rfc1766={$SQL->quote($this->lng)}")) {
					$this->id = $r[0];
				}
			}
			return $this->id;
		}
		if ('lng' === $key) { return $this->lng; }
		if ('array' === $key) { return self::$data[$this->lng]; }
		if ('multilingual' === $key) { return 1<count(self::active()); }
		return $this->get($key);
	}

	function __set($key, $value)
	{
		if ('lng' === $key && preg_match(self::REGEX, $value))
		{
			$this->lng = $value;
			$this->initCore();
		} else if (isset(self::$data[$this->lng][$key])) {
			self::$data[$this->lng][$key] = $value;
		}
	}

	function __toString() { return $this->lng; }

	public  function init() {}

	protected function initCore()
	{
		if (!isset(self::$data[$this->lng]))
		{
			self::$data[$this->lng] = array();
			$this->load('dragonfly_kernel');
			if (defined('ADMIN_PAGES')) {
				$this->load('dragonfly_admin');
			}
		}
	}

	public static function active($lng=null)
	{
		$K = Dragonfly::getKernel();
		static $languages = array();
		if (!$K || !$K->SQL || !$K->SQL->TBL || !isset($K->SQL->TBL->l10n)) {
			return is_file('includes/l10n/'.$lng.'/dragonfly/kernel.php');
		}
		if (!$languages && !($languages = $K->CACHE->get('l10n_active')))
		{
			$result = $K->SQL->query('SELECT l10n_rfc1766 FROM '.$K->SQL->TBL->l10n.' WHERE l10n_active>0');
			while ($row = $result->fetch_row()) { $languages[$row[0]] = 1; }
			$result->free();
			if (!$languages) { $languages = array('en' => 1); }
			$K->CACHE->set('l10n_active', $languages);
		}
		if (is_null($lng)) { return $languages; }
		return isset($languages[$lng]);
	}

	public static function getFullList()
	{
		if (!is_array(self::$all)) {
			$SQL = Dragonfly::getKernel()->SQL;
			if ($SQL && isset($SQL->TBL->l10n) && isset($SQL->TBL->l10n_translate)) {
				self::$all = array();
				$qr = $SQL->query("SELECT
					l10n_id id,
					l10n_rfc1766 dir,
					l10n_active active,
					l10n_iso639_1 iso639_1,
					l10n_iso639_2 iso639_2,
					lt.*
				FROM {$SQL->TBL->l10n}
				LEFT JOIN {$SQL->TBL->l10n_translate} lt ON (msg_id=l10n_rfc1766)");
				while ($r = $qr->fetch_assoc()) {
					$r['active'] = ($r['active'] && is_file('includes/l10n/'.$r['dir'].'/dragonfly/kernel.php'));
					self::$all[$r['dir']] = $r;
				}
			}

			$dir = dir('includes/l10n');
			while ($file = $dir->read()) {
				if ($file[0]!=='.' && !isset(self::$all[$file]) && is_file('includes/l10n/'.$file.'/dragonfly/kernel.php')) {
					self::$all[$file] = array(
						'id' => null,
						'dir' => $file,
						'active' => 0,
						'iso639_1' => '',
						'iso639_2' => '',
						'en' => $file
					);
				}
			}
			$dir->close();
		}
		return self::$all;
	}

	public function getNameForDir($dir)
	{
		if ($dir) {
			$list = self::getFullList();
			if ($list && isset($list[$dir])) {
				$lng = $list[$dir];
				return empty($lng[$this->lng]) ? $lng['en'] : $lng[$this->lng];
			}
		}
		return false;
	}

	public function getActiveList() { return $this->getList(1); }

	public function getInactiveList() { return $this->getList(0); }

	protected function getList($active)
	{
		$active = intval($active);
		$cache_key = 'l10n_'.$this->lng.'_'.$active;
		$K = Dragonfly::getKernel();
		if (!($langs = $K->CACHE->get($cache_key)))
		{
			$result = $K->SQL->query("SELECT l10n_rfc1766, l10n_id, t.*
			FROM {$K->SQL->TBL->l10n}
			LEFT JOIN {$K->SQL->TBL->l10n_translate} t ON (msg_id = l10n_rfc1766)
			WHERE l10n_active={$active}
			ORDER BY ".str_replace('-','_',$this->lng));
			while ($row = $result->fetch_assoc()) {
				$langs[] = array(
					'id'    => $row['l10n_id'],
					'label' => $row[$this->lng],
					'title' => $row[str_replace('-','_',$row['l10n_rfc1766'])],
					'value' => $row['l10n_rfc1766']
				);
			}
			$result->free();
			$K->CACHE->set($cache_key, $langs);
		}
		return $langs;
	}

	/**
	 * $name will be looked up as:
	 *     poodle_libname
	 *         includes/poodle/{libname}/l10n/{lng}.php
	 *         includes/l10n/{lng}/poodle/{libname}.php
	 *     dragonfly_libname
	 *         includes/dragonfly/{libname}/l10n/{lng}.php
	 *         includes/l10n/{lng}/dragonfly/{libname}.php
	 *     modulename
	 *         modules/{modulename}/l10n/{lng}.php
	 *         includes/l10n/{lng}/{modulename}.php
	 *
	 * if {lng} has a value like 'nl-be' it will also look in: 'nl' and 'en'
	 */
	protected static $loaded_files = array();
	public function load($name, $skip_error=false)
	{
		$name = strtolower($name);
		if ('exception' === $name) { return false; }
		if (!$name) {
			if (!$skip_error) { \Poodle\Debugger::trigger(sprintf($this->get('_NO_PARAM'), __CLASS__ . '::load()', 'filename'), __FILE__, E_USER_NOTICE); }
			return false;
		}

		$lng = $this->lng;

		$file = "{$lng}/{$name}";
		if (in_array($file,self::$loaded_files)) {
//			$bt = debug_backtrace(0);
//			trigger_error("L10N::load($name) again by {$bt[0]['file']}#{$bt[0]['line']}");
			return true;
		}
		self::$loaded_files[] = $file;

		$files = array();
		preg_match('#^(?:(dragonfly|poodle)_)?(.+)$#D',$name,$m);
		while ($lng) {
			if ($m[1]) {
				$files[] = "{$m[1]}/{$m[2]}/l10n/{$lng}.php";
				$files[] = "includes/l10n/{$lng}/{$m[1]}/{$m[2]}.php";
			} else {
				$files[] = "modules/{$m[2]}/l10n/{$lng}.php";
				$files[] = "includes/l10n/{$lng}/{$m[2]}.php";
			}

			if (3<strlen($lng)) { $lng = substr($lng, 0, -3); }
			else if ('en' !== $lng) { $lng = 'en'; }
			else $lng = null;
		}

		$file = false;
		foreach ($files as $file) {
			if ($file = Dragonfly::getFile($file)) {
				include($file);
				break;
			}
		}

		if (!$file) {
			if (!$skip_error) { \Poodle\Debugger::trigger(sprintf($this->get('_NO_L10NF'), $name), __FILE__, E_USER_NOTICE); }
			return false;
		}

		if (empty($LNG)) {
			return false;
		}

		self::$data[$this->lng] = array_merge(self::$data[$this->lng], $LNG);
		return true;
	}

	public function get($var, $var2=null)
	{
		if (!strlen($var)) return '';

		$LNG = &self::$data[$this->lng];
		$cf = false;
		$txt = $var;
		if (!isset($LNG[$txt])) {
			if (!$var2) {
				list($txt,$cf) = self::cfirst($txt);
			}
			if (!isset($LNG[$txt])) {
				// Old language system uses constants
				if (defined($var)) { return strip_tags(constant($var)); }
				\Poodle\Debugger::trigger(sprintf($LNG['_NO_L10NV'], $var), __FILE__, E_USER_NOTICE);
				$LNG[$var] = $var;
				return $var;
			}
		}
		$txt = &$LNG[$txt];
		if (isset($var2)) {
			if (!isset($txt[$var2])) {
				list($var2,$cf) = self::cfirst($var2);
				if (!isset($txt[$var2])) {
					\Poodle\Debugger::trigger(sprintf($LNG['_NO_L10NV'], $var.']['.$var2), __FILE__, E_USER_NOTICE);
					return ($cf ? $cf($var2) : $var2);
				}
			}
			$txt = &$txt[$var2];
		}
		if ($cf) $txt = \Poodle\Unicode::$cf(mb_substr($txt, 0, 1)).mb_substr($txt, 1);
		return $txt;
	}

	protected static function cfirst($str)
	{
		$i = ord($str);
		if ($i >= 0x41 && $i <= 0x5A)
			return array(lcfirst($str), 'ucfirst');
		if ($i >= 0x61 && $i <= 0x7A)
			return array(ucfirst($str), 'lcfirst');
		return array($str, false);
	}

	/*
	 * About Plural array's:
	 * If your language is not compatible with 2 options (default output: 1 comment, 0 comments, 10 comments)
	 * then add a callback function as:	'plural_cb'=>'function_name'
	 * where function_name($number) should return the value index, Russian example:
	 *
	 * $LNG['%d noteboook'] = array('%d тетрадь', '%d тетради', '%d тетрадей', 'plural_cb'=>'ru_plural_noteboook');
	 * function ru_plural_noteboook($n) { return $n%10==1 && $n%100!=11 ? 0 : $n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2; }
	 *
	 * Another example is 'sheep' which is in english 1 sheep, 2 sheep.
	 * In Dutch for example it is: 1 schaap, 2 schapen
	 * This is easily achieved as:
	 *     en: $LNG['%d sheep'] = '%d sheep';
	 *     nl: $LNG['%d sheep'] = array('%d schaap','%d schapen');
	 */
	public function nget($n, $var, $var2=null)
	{
		$str = $this->get($var, $var2);
		if (is_array($str)) {
			$i = (1 == $n ? 0 : 1);
			if (!empty($str['plural_cb']) && function_exists($str['plural_cb'])) {
				$i = $str['plural_cb']($n);
			}
			unset($str['plural_cb']);
			$str = $str[min(max(0,$i),count($str)-1)];
		}
		return $str;
	}

	public function plural($n, $var, $var2=null)
	{
		return sprintf($this->nget($n, $var, $var2), $n);
	}

	# crop filesize to human readable format
	public function filesizeToHuman($size, $precision=2)
	{
		if (!is_int($size) && !ctype_digit($size)) { return null; }
		$size = max($size, 0);
		$i = ($size > 0) ? floor(log($size, 1024)) : 0;
		if ($i>0) { $size /= pow(1024, $i); }
		else { $precision = 0; }
		return sprintf($this->get('_FILESIZES', $i), $this->round($size, max(0, $precision)));
	}

	# language specific number format
	public function round($size, $precision=0)
	{
		return number_format($size, $precision, $this->get('_seperator', 0), $this->get('_seperator', 1));
	}

	/* Date-Time Methods */

	public static function ISO_d($time=false)  { return gmdate('Y-m-d', ($time?$time:time())); }
	public static function ISO_t($time=false)  { return gmdate('H:i:s', ($time?$time:time())); }
	public static function ISO_dt($time=false) { return gmdate('Y-m-d H:i:s', ($time?$time:time())); }

	public function date($format, $time=false, $timezone=false)
	{
		if (isset(self::$data[$this->lng]['_time']['formats'][$format])) { $format = self::$data[$this->lng]['_time']['formats'][$format]; }
		$count = 0;
		$format = str_replace(array('D', 'l', 'F', 'M'), array('_\Dw', '_\lw', '_\Fn', '_\Mn'), $format, $count);
		# Other timezone?
		if ($timezone) {
			$deftzone = date_default_timezone_get();
			$timezone = date_default_timezone_set($timezone);
		}
		if (is_string($time) && !ctype_digit($time)) { $time = strtotime($time); }
		# parce the formatted string
		$time = date($format, ($time?$time:time()));
		if ($timezone) { date_default_timezone_set($deftzone); }
		return (0 === $count) ? $time : preg_replace_callback('#_([DlFM])(\d{1,2})#', array($this, 'date_cb'), $time);
	}
	protected function date_cb($params) { return self::$data[$this->lng]['_time'][$params[1]][intval($params[2])]; }

	public function timeReadable($time, $format='%x')
	{
		$rep  = array();
		$desc = array(31536000 => '%d years', 2628000 => '%d months', 604800 => '%d weeks', 86400 => '%d days', 3600 => '%d hours', 60 => '%d minutes', 1 => '%d seconds');
		foreach ($desc as $k => $s) {
			$rest = floor($time/$k);
			if ($rest > 0) {
				$time -= ($rest*$k);
				$rest = self::plural($rest, $s);
				if ('%x' === $format) { return $rest; }
			} else {
				$rest = '';
			}
			$rep[] = $rest;
		}
		return ('%x' === $format) ? '' : str_replace(array('%y','%m','%w','%d','%h','%i','%s'), $rep, $format);
	}

	public function timezones(/*$zone*/)
	{
		# DateTimeZone::AFRICA||DateTimeZone::AMERICA||DateTimeZone::ANTARCTICA||DateTimeZone::ARCTIC||DateTimeZone::ASIA||DateTimeZone::ATLANTIC||DateTimeZone::AUSTRALIA||DateTimeZone||DateTimeZone::INDIAN||DateTimeZone::PACIFIC
		$tz = timezone_identifiers_list(); # DateTimeZone::listIdentifiers(2047); DateTimeZone::ALL
		sort($tz);
		$timezones = array('UTC'=>'UTC');
		foreach ($tz as $v)
		{
			if (preg_match('#^(Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/(.*)$#',$v,$m))
			{
				$k = $m[1];
				$timezones[$k][''] = $k;
				$timezones[$k][$v] = str_replace('_', ' ', $m[2]);
			}
		}
		self::$data[$this->lng]['_timezones'] = $timezones;
		return self::$data[$this->lng]['_timezones'];
	}

	# ArrayAccess
	public function offsetExists($k)  { return array_key_exists($k, self::$data[$this->lng]); }
	public function offsetGet($k)     { return $this->__get($k); }
	public function offsetSet($k, $v) { $this->__set($k, $v); }
	public function offsetUnset($k)   {}
}
